package org.jiuling.crypto.algorithm;

import org.jiuling.basics.converter.ByteConvert;
import org.jiuling.basics.converter.StringConvert;
import org.jiuling.basics.validation.BooleanVerify;
import org.jiuling.basics.validation.IntegerVerify;
import org.jiuling.basics.validation.ObjectVerify;
import org.jiuling.crypto.exception.CryptoRuntimeException;

import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;

/**
 * PBKDF2加密
 * @author 紫灵
 * @date 2024年06月27日 22时38分37秒
 */
public class PBKDF2 {

    /** 默认算法 */
    private static final PBKDF2AlgorithmEnum DEFAULT_ALGORITHM = PBKDF2AlgorithmEnum.PBKDF2_WITH_HMAC_SHA256;
    /** 默认密钥长度 */
    private static final int DEFAULT_KEY_LENGTH = 256;
    /** 默认循环次数 */
    private static final int DEFAULT_ITERATIONS = 10000;
    /** 默认是否转换为Base64 */
    private static final boolean DEFAULT_TO_BASE64 = false;

    /**
     * 获取加密
     * @param password 密码
     * @param salt 盐
     * @return 加密数据
     */
    public static String getEncodedData(String password,String salt){
        return getEncodedData(password,salt,DEFAULT_TO_BASE64);
    }

    /**
     * 获取加密
     * @param password 密码
     * @param salt 盐
     * @param toBase64 是否转换为Base64
     * @return 加密数据
     */
    public static String getEncodedData(String password,String salt,Boolean toBase64){
        return getEncodedData(null,password,salt,null,null,toBase64);
    }

    /**
     * 获取加密
     * @param password 密码
     * @param salt 盐
     * @param keyLength 加密长度
     * @return 加密数据
     */
    public static String getEncodedData(String password,String salt,Integer keyLength){
        return getEncodedData(null,password,salt,null,keyLength,null);
    }

    /**
     * 获取加密
     * @param algorithmEnum 算法枚举
     * @param password 密码
     * @param salt 盐值
     * @param iterations 循环次数
     * @param keyLength 密钥长度
     * @param toBase64 转换为Base64
     * @return 加密
     */
    public static String getEncodedData(PBKDF2AlgorithmEnum algorithmEnum,String password,String salt,Integer iterations,Integer keyLength,Boolean toBase64){
        // 算法
        algorithmEnum = ObjectVerify.isNull(algorithmEnum) ? DEFAULT_ALGORITHM : algorithmEnum;
        // 密钥长度
        keyLength = IntegerVerify.isBlank(keyLength) ? DEFAULT_KEY_LENGTH * 4 : keyLength * 4;
        // 循环次数
        iterations = IntegerVerify.isBlank(iterations) ? DEFAULT_ITERATIONS : iterations;
        // 是否转换为Base64
        toBase64 = BooleanVerify.isNull(toBase64) ? DEFAULT_TO_BASE64 : toBase64;

        // 获取密钥工厂
        SecretKeyFactory keyFactory;
        try {
            keyFactory = SecretKeyFactory.getInstance(algorithmEnum.getAlgorithm());
        } catch (NoSuchAlgorithmException e) {
            throw new CryptoRuntimeException(StringConvert.format("PBKDF2算法{}不存在:","{}",algorithmEnum.getAlgorithm()));
        }
        KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), iterations, keyLength);

        // 获取密钥
        SecretKey secret;
        try {
            secret = keyFactory.generateSecret(keySpec);
        } catch (InvalidKeySpecException e) {
            throw new CryptoRuntimeException(StringConvert.format("无法生成密钥:{}","{}",e.getLocalizedMessage()));
        }

        if (toBase64) {
            //使用Base64进行转码密文
            return Base64.getEncoder().encodeToString(secret.getEncoded());
        }else {
            return ByteConvert.byteArrayToHexString(secret.getEncoded());
        }
    }

    public enum PBKDF2AlgorithmEnum{

        PBKDF2_WITH_HMAC_SHA256("PBKDF2WithHmacSHA256","使用HMAC-SHA256作为底层伪随机函数的PBKDF2实现"),
        PBKDF2_WITH_HMAC_SHA1("PBKDF2WithHmacSHA1","使用较旧的HMAC-SHA1作为伪随机函数的PBKDF2实现,安全性不如SHA-256"),
        PBKDF2_WITH_HMAC_SHA512("PBKDF2WithHmacSHA512","使用HMAC-SHA512的PBKDF2实现，安全性比SHA-256强，但是密钥长度也较长");

        private final String algorithm;
        private final String desc;

        PBKDF2AlgorithmEnum(String algorithm,String desc){
            this.desc = desc;
            this.algorithm = algorithm;
        }

        public String getDesc() {
            return desc;
        }

        public String getAlgorithm() {
            return algorithm;
        }
    }

}
